The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
AUTHORS 01
CHANGES 153
MANIFEST 315
MANIFEST.SKIP 01
META.yml 23
Makefile.PL 27
examples/dancr/dancr.pl 21
lib/Dancer/App.pm 04
lib/Dancer/Config.pm 132
lib/Dancer/Cookbook.pod 33
lib/Dancer/Development/Integration.pod 33
lib/Dancer/Development.pod 33
lib/Dancer/Error.pm 18
lib/Dancer/Factory/Hook.pm 074
lib/Dancer/HTTP.pm 12
lib/Dancer/Handler/PSGI.pm 147
lib/Dancer/Handler.pm 310
lib/Dancer/Hook/Properties.pm 059
lib/Dancer/Hook.pm 0135
lib/Dancer/Logger/Abstract.pm 11
lib/Dancer/Plugin.pm 22
lib/Dancer/Renderer.pm 1226
lib/Dancer/Request.pm 1854
lib/Dancer/Response.pm 613
lib/Dancer/Route/Registry.pm 331
lib/Dancer/Route.pm 01
lib/Dancer/Serializer.pm 07
lib/Dancer/Session.pm 02
lib/Dancer/Template/Abstract.pm 214
lib/Dancer/Test.pm 159
lib/Dancer/Tutorial.pod 45
lib/Dancer.pm 53236
script/dancer 1121
t/00_base/003_syntax.t 01
t/00_base/004_args.t 112
t/01_config/03_logger.t 33
t/03_route_handler/02_before_filter.t 730
t/03_route_handler/06_regexp.t 46
t/03_route_handler/11_redirect.t 011
t/03_route_handler/26_after_hook.t 280
t/03_route_handler/29_forward.t 716
t/05_views/03_layout.t 22
t/10_template/06_before_template_hook.t 440
t/11_logger/11_runtime_file.t 030
t/14_serializer/17_clear_serializer.t 52
t/15_plugins/02_config.t 01
t/22_hooks/00_syntax.t 018
t/22_hooks/01_api.t 023
t/22_hooks/02_before.t 071
t/22_hooks/03_after.t 027
t/22_hooks/04_template.t 038
t/22_hooks/05_layout.t 035
t/22_hooks/06_serializer.t 075
t/22_hooks/07_file.t 042
t/22_hooks/08_error.t 033
t/22_hooks/views/index.tt 01
t/22_hooks/views/layouts/main.tt 02
t/lib/MyAppFoo.pm 22
58 files changed (This is a version diff) 3651264
@@ -6,3 +6,4 @@
  Franck Cuny <franck@lumberjaph.net>
  Naveed Massjouni <naveedm9@gmail.com>
  Damien 'dams' Krotkine <dams@cpan.org>
+ Alberto Simões <ambs+dancer@perl-hackers.net>
@@ -1,4 +1,52 @@
-{{$NEXT}}
+1.3050      20.05.2011
+    ** Codename: The Captain Hook Adventure // Franck Cuny **
+
+    [ ENHANCEMENTS ]
+    * No functional changes, just releasing as stable.
+
+    [ DOCUMENTATION ]
+    * Improve Dancer documentation
+      (Damien Krotkine)
+
+1.3049_01   14.05.2011
+
+    [ API CHANGES ]
+    * Deprecation of 'logger' (use set). (Alberto Simões)
+    * Deprecate 'layout' (use set). (Alberto Simões)
+    * Definitely remove plack_middlewares HashRef deprecation.
+      (Alberto Simões & Damien Krotkine)
+
+    [ BUG FIXES ]
+    * Unbreaking auto_page somewhat - the catch-all route added will
+      pass unless a suitable view exists.  This means that routes like
+      /foo are not obscured, and made up URLs will result in a proper
+      404, not 500.  A little more work required here, though. 
+      (David Precious)
+    * Anchor regular expression routes. Before regular expressions
+      were matching anywhere in the URL.
+      (Alberto Simões)
+
+    [ ENHANCEMENTS ]
+    * GH #519: remove redundant lines from CSS
+      (Alberto Simões)
+    * When scaffolding an app, show a warning if YAML not installed.
+      Prompted by Issue 496.  (David Precious)
+    * Hooks! add new positions for hooks, and possibility to create
+      your own hooks inside your application and your plugin.
+      (Franck Cuny)
+    * Don't try to read/set session vars with empty/undef keys.  It
+      doesn't make sense to do so, and can cause warnings elsewhere.
+      (David Precious)
+    * Check HTTP status code/alias passed to status() is valid;
+      previously, and invalid code would result in the response status
+      being unset
+      (David Precious, prompted on IRC by jonas)
+    * Lowercase status aliases and swap spaces for underscores before
+      trying to match
+      (David Precious, suggested on IRC by jonas)
+    * Added 'behind_proxy' setting, making Dancer honor
+      X_FORWARDED_PROTOCOL and X_FORWARDED_HOST
+      (Alberto Simões, requested by sukria and others)
 
 1.3040      01.05.2011
     ** Codename: Yanick in Black // Yanick Champoux, Labor Day - May Day **
@@ -23,8 +71,12 @@
       (Sawyer X)
     * Rewrite scalar usage of qw() that is incompatible with 5.14.
       (Alberto Simões)
+    * Don't parse ARGV when under PSGI (closes #473)
+      (Franck Cuny)
 
     [ ENHANCEMENTS ]
+    * Forward can change method GH#493
+      (Alberto Simões)
     * Introducing the "megasplat"!
       (Yanick Champoux)
     * More tests for versions, setings and variables.
@@ -22,12 +22,15 @@ lib/Dancer/Development.pod
 lib/Dancer/Development/Integration.pod
 lib/Dancer/Engine.pm
 lib/Dancer/Error.pm
+lib/Dancer/Factory/Hook.pm
 lib/Dancer/FileUtils.pm
 lib/Dancer/GetOpt.pm
 lib/Dancer/Handler.pm
 lib/Dancer/Handler/Debug.pm
 lib/Dancer/Handler/PSGI.pm
 lib/Dancer/Handler/Standalone.pm
+lib/Dancer/Hook.pm
+lib/Dancer/Hook/Properties.pm
 lib/Dancer/HTTP.pm
 lib/Dancer/Introduction.pod
 lib/Dancer/Logger.pm
@@ -135,7 +138,6 @@ t/03_route_handler/000_create_fake_env.t
 t/03_route_handler/00_http_methods.t
 t/03_route_handler/00_route_object.t
 t/03_route_handler/01_params.t
-t/03_route_handler/02_before_filter.t
 t/03_route_handler/03_passing.t
 t/03_route_handler/04_wildcards.t
 t/03_route_handler/04_wildcards_megasplat.t
@@ -160,7 +162,6 @@ t/03_route_handler/22_filter_halt.t
 t/03_route_handler/23_filter_error_catching.t
 t/03_route_handler/24_multiple_params.t
 t/03_route_handler/24_named_captures.t
-t/03_route_handler/26_after_hook.t
 t/03_route_handler/27_issue_77_pass_breaks_routes.t
 t/03_route_handler/28_plack_mount.t
 t/03_route_handler/29_forward.t
@@ -232,7 +233,6 @@ t/10_template/01_factory.t
 t/10_template/02_abstract_class.t
 t/10_template/03_simple.t
 t/10_template/05_template_toolkit.t
-t/10_template/06_before_template_hook.t
 t/10_template/extension.t
 t/10_template/index.txt
 t/10_template/template.t
@@ -251,6 +251,7 @@ t/11_logger/07_diag.t
 t/11_logger/08_serialize.t
 t/11_logger/09_capture.t
 t/11_logger/10_note.t
+t/11_logger/11_runtime_file.t
 t/12_response/000_create_fake_env.t
 t/12_response/01_CRLF_injection.t
 t/12_response/02_headers.t
@@ -307,6 +308,17 @@ t/19_dancer/01_script.t
 t/19_dancer/02_script_version_from.t
 t/20_deprecation/01_api.t
 t/21_dependents/Dancer-Session-Cookie.t
+t/22_hooks/00_syntax.t
+t/22_hooks/01_api.t
+t/22_hooks/02_before.t
+t/22_hooks/03_after.t
+t/22_hooks/04_template.t
+t/22_hooks/05_layout.t
+t/22_hooks/06_serializer.t
+t/22_hooks/07_file.t
+t/22_hooks/08_error.t
+t/22_hooks/views/index.tt
+t/22_hooks/views/layouts/main.tt
 t/lib/EasyMocker.pm
 t/lib/Forum.pm
 t/lib/LinkBlocker.pm
@@ -13,3 +13,4 @@ Makefile$
 ^run_perltidy.sh$
 ^.*\.swp$
 lib/Dancer/Plugin/WebSocket.pm
+^.*~$
\ No newline at end of file
@@ -1,6 +1,6 @@
 --- #YAML:1.0
 name:               Dancer
-version:            1.3040
+version:            1.3050
 abstract:           A minimal-effort oriented web application framework
 author:  []
 license:            perl
@@ -8,7 +8,8 @@ distribution_type:  module
 configure_requires:
     ExtUtils::MakeMaker:  0
 build_requires:
-    Test::More:  0.94
+    Test::More:           0.94
+    Tie::Hash::NamedCapture:  0
 requires:
     Encode:               0
     File::Basename:       0
@@ -13,6 +13,10 @@ tags:
         --exclude=.git \
         --exclude='*.swp' \
         --languages=Perl --langmap=Perl:+.t 
+
+author_test:
+	RELEASE_TESTING=1 $(MAKE) test
+
 MAKE_FRAG
 }
 
@@ -22,7 +26,7 @@ WriteMakefile1(
     LICENSE      => 'perl',
     VERSION_FROM => 'lib/Dancer.pm',
     EXE_FILES    => ['script/dancer'],
-    
+
     META_MERGE => {
         resources => {
             repository => 'http://github.com/sukria/Dancer',
@@ -32,7 +36,8 @@ WriteMakefile1(
     },
 
     BUILD_REQUIRES => {
-        'Test::More'             => '0.94',
+        'Test::More'              => '0.94',
+        'Tie::Hash::NamedCapture' => '0',
     },
 
     PREREQ_PM => {
@@ -14,8 +14,7 @@ set 'startup_info' => 1;
 set 'warnings'     => 1;
 set 'username'     => 'admin';
 set 'password'     => 'password';
-
-layout 'main';
+set 'layout'       => 'main';
 
 my $flash;
 
@@ -76,6 +76,10 @@ sub find_route_through_apps {
     my ($class, $request) = @_;
     for my $app (Dancer::App->applications) {
         my $route = $app->find_route($request);
+        if ($route) {
+            Dancer::App->current($route->app);
+            return $route;
+        }
         return $route if $route;
     }
     return;
@@ -57,7 +57,11 @@ my $setters = {
                 'get', '/:page',
                 sub {
                     my $params = Dancer::SharedData->request->params;
-                    Dancer::template($params->{'page'});
+                    if  (-f Dancer::engine('template')->view($params->{page})) {
+                        return Dancer::template($params->{'page'});
+                    } else {
+                        return Dancer::pass();
+                    }
                 }
             );
         }
@@ -287,6 +291,11 @@ B<--port> switch.
 If set to true, runs the standalone webserver in the background.
 This setting can be changed on the command-line with the B<--daemon> flag.
 
+=head3 behind_proxy (boolean)
+
+If set to true, Dancer will look to C<X-Forwarded-Protocol> and
+C<X-Forwarded-host> when constructing URLs (for example, when using
+C<redirect>. This is useful if your application is behind a proxy.
 
 =head2 Content type / character set
 
@@ -503,6 +512,28 @@ use them.
 
 See L<Dancer::Session> for supported engines and their respective configuration.
 
+=head3 session_expires
+
+The session expiry time in seconds, or as e.g. "2 hours" (see
+L<Dancer::Cookie/expires>.  By default, there is no specific expiry time.
+
+=head3 session_name
+
+The name of the cookie to store the session ID in.  Defaults to
+C<dancer.session>.  This can be overridden by certain session engines.
+
+=head3 session_secure
+
+The user's session ID is stored in a cookie.  If the C<session_secure> setting
+is set to a true value, the cookie will be marked as secure, meaning it should
+only be sent over HTTPS connections.
+
+=head3 session_is_http_only
+
+This setting defaults to 1 and instructs the session cookie to be
+created with the C<HttpOnly> option active, meaning that JavaScript
+will not be able to access to its value.
+
 
 =head2 auto_page (boolean)
 
@@ -421,8 +421,8 @@ keyword, and L<Crypt::SaltedHash> to handle salted hashed passwords (well, you
 wouldn't store your users passwords in the clear, would you?)) follows:
 
     post '/login' => sub {
-        my $user = database()->selectrow_hashref(
-            'select * from users where username = ?', {}, params->{user}
+        my $user = database->quick_select('users', 
+            { username => params->{user} }
         );
         if (!$user) {
             warning "Failed login for unrecognised user " . params->{user};
@@ -527,7 +527,7 @@ Here is an example of a layout: C<views/layouts/main.tt> :
 You can tell your app which layout to use with C<layout: name> in the config
 file, or within your code:
 
-    layout 'main';
+    set layout => 'main';
 
 You can control which layout to use (or whether to use a layout at all) for a
 specific request without altering the layout setting by passing an options
@@ -8,7 +8,7 @@ This documentation describes the procedure used for integrators to review and
 merge contributions sent via pull-requests.
 
 Every core-team member should read and apply the procedures described
-here. This will allow for a better history and more consitency in our
+here. This will allow for a better history and more consistency in our
 ways of handling the (increasing number!) of pull requests.
 
 =head1 TERMS
@@ -53,7 +53,7 @@ Let's say the user I<$user> has sent a PR, he has followed the
 instructions described in L<Dancer::Development> so his work is based
 on the integration branch (C<devel>).
 
-All the procedure described here is designed to avoid unecessary
+All the procedure described here is designed to avoid unnecessary
 recursive-merge, in order to keep a clean and flat history in the
 integration branch.
 
@@ -132,7 +132,7 @@ shiny commits we want.
 Those release are built with git-flow (with C<git-flow release>) and are then
 uploaded to CPAN.
 
-Since Dancer 1.2, we also have another parallell release cycle which is what we
+Since Dancer 1.2, we also have another parallel release cycle which is what we
 call the I<frozen> branch. It's a maintenance-only release cycle. That branch is
 created from the tag of the first release of a I<stable> version (namely a
 release series with an even minor number).
@@ -148,8 +148,8 @@ your clone (C<master> is used only for building releases).
     $ git checkout -b devel upstream/devel
 
 This will create a local branch in your clone named C<devel> and that
-will track the offical C<devel> branch. That way, if you have more or
-less commits than the upstream repo, you'll be immediatly notified by git.
+will track the official C<devel> branch. That way, if you have more or
+less commits than the upstream repo, you'll be immediately notified by git.
 
 =item *
 
@@ -264,7 +264,7 @@ Official developers have write access to this repository, contributors are
 invited to fork it if they want to submit patches, as explained in the
 I<Patch submission> section.
 
-The repository layout is organized as follows:
+The repository layout is organised as follows:
 
 =over 4
 
@@ -10,10 +10,14 @@ use Dancer::Response;
 use Dancer::Renderer;
 use Dancer::Config 'setting';
 use Dancer::Logger;
+use Dancer::Factory::Hook;
 use Dancer::Session;
 use Dancer::FileUtils qw(open_file);
 use Dancer::Engine;
 
+Dancer::Factory::Hook->instance->install_hooks(
+    qw/before_error_render after_error_render/);
+
 sub init {
     my ($self) = @_;
 
@@ -166,7 +170,10 @@ sub render {
     my $self = shift;
 
     my $serializer = setting('serializer');
-    $serializer ? $self->_render_serialized() : $self->_render_html();
+    Dancer::Factory::Hook->instance->execute_hooks('before_error_render', $self);
+    my $response = $serializer ? $self->_render_serialized() : $self->_render_html();
+    Dancer::Factory::Hook->instance->execute_hooks('after_error_render', $response);
+    $response;
 }
 
 sub _render_serialized {
@@ -0,0 +1,74 @@
+package Dancer::Factory::Hook;
+
+use strict;
+use warnings;
+use Carp;
+
+use base 'Dancer::Object::Singleton';
+
+__PACKAGE__->attributes(qw/ hooks registered_hooks/);
+
+sub init {
+    my ( $class, $self ) = @_;
+    $self->hooks( {} );
+    $self->registered_hooks( [] );
+    return $self;
+}
+
+sub install_hooks {
+    my ( $self, @hooks_name ) = @_;
+
+    if ( !scalar @hooks_name ) {
+        croak "at least one name is required";
+    }
+
+    foreach my $hook_name (@hooks_name) {
+        if ( $self->hook_is_registered($hook_name) ) {
+            croak "$hook_name is already regsitered, please use another name";
+        }
+        $self->_add_hook( $hook_name );
+    }
+}
+
+sub register_hook {
+    my ( $self, $hook ) = @_;
+    $self->_add_registered_hook( $hook->name, $hook->code );
+}
+
+sub _add_registered_hook {
+    my ($class, $hook_name, $compiled_filter) = @_;
+    push @{$class->hooks->{$hook_name}}, $compiled_filter;
+}
+
+sub _add_hook {
+    my ($self, $hook_name ) = @_;
+    push @{$self->registered_hooks}, $hook_name;
+}
+
+sub hook_is_registered {
+    my ( $self, $hook_name ) = @_;
+    return grep { $_ eq $hook_name } @{$self->registered_hooks};
+}
+
+sub execute_hooks {
+    my ($self, $hook_name, @args) = @_;
+
+    croak("Can't ask for hooks without a position") unless $hook_name;
+
+    if (!$self->hook_is_registered($hook_name)){
+        croak("The hook '$hook_name' doesn't exists");
+    }
+
+   $_->(@args) foreach @{$self->get_hooks_for($hook_name)};
+}
+
+sub get_hooks_for {
+    my ( $self, $hook_name ) = @_;
+
+    croak("Can't ask for hooks without a position") unless $hook_name;
+
+    $self->hooks->{$hook_name} || [];
+}
+
+
+1;
@@ -78,7 +78,8 @@ $HTTP_CODES->{error} = $HTTP_CODES->{internal_server_error};
 sub status {
     my ($class, $name) = @_;
     return $name if $name =~ /^\d+$/;
-    return $HTTP_CODES->{$name};
+    $name =~ s/\s/_/;
+    return $HTTP_CODES->{lc $name};
 }
 
 1;
@@ -69,20 +69,13 @@ sub apply_plack_middlewares {
 
     my $builder = Plack::Builder->new();
 
-    # XXX remove this after 1.2
-    if ( ref $middlewares eq 'HASH' ) {
-        Dancer::Deprecation->deprecated(
-            fatal   => 1,
-            feature => 'Listing Plack middlewares as a hash ref',
-            reason  => 'Must be listed as an array ref',
-        );
-    }
-    else {
-        map {
-            Dancer::Logger::core "add middleware " . $_->[0];
-            $builder->add_middleware(@$_)
-        } @$middlewares;
-    }
+    ref $middlewares eq "ARRAY"
+      or croak "'plack_middlewares' setting must be an ArrayRef";
+
+    map {
+        Dancer::Logger::core "add middleware " . $_->[0];
+        $builder->add_middleware(@$_)
+    } @$middlewares;
 
     $app = $builder->to_app($app);
 
@@ -68,13 +68,20 @@ sub handle_request {
         Dancer::App->reload_apps;
     }
 
-    eval {
+    render_request($request);
+    return $self->render_response();
+}
+
+sub render_request {
+    my $request = shift;
+    my $action;
+    $action = eval {
         Dancer::Renderer->render_file
           || Dancer::Renderer->render_action
           || Dancer::Renderer->render_error(404);
     };
     if ($@) {
-        Dancer::Logger::core(
+        Dancer::Logger::error(
             'request to ' . $request->path_info . " crashed: $@");
 
         Dancer::Error->new(
@@ -83,7 +90,7 @@ sub handle_request {
             message => $@
         )->render();
     }
-    return $self->render_response();
+    return $action;
 }
 
 sub psgi_app {
@@ -0,0 +1,59 @@
+package Dancer::Hook::Properties;
+
+use strict;
+use warnings;
+
+use base 'Dancer::Object';
+
+Dancer::Hook::Properties->attributes(qw/apps/);
+
+sub init {
+    my ($self, %args) = @_;
+
+    $self->_init_apps(\%args);
+    return $self;
+}
+
+sub _init_apps {
+    my ( $self, $args ) = @_;
+    if ( my $apps = $args->{'apps'} ) {
+        ref $apps ? $self->apps($apps) : $self->apps( [$apps] );
+        return;
+    }
+    else {
+        $self->apps( [] );
+    }
+}
+
+sub should_run_this_app {
+    my ( $self, $app ) = @_;
+
+    return 1 unless scalar( @{ $self->apps } );
+
+    if ( $self->apps ) {
+        return grep { $_ eq $app } @{ $self->apps };
+    }
+}
+
+1;
+
+=head1 NAME
+
+Dancer::Hook::Properties - Properties attached to a hook
+
+=head1 DESCRIPTION
+
+Properties attached to a hook
+
+=head1 SYNOPSIS
+
+=head1 METHODS
+
+=head1 AUTHORS
+
+This module has been written by Alexis Sukrieh and others.
+
+=head1 LICENSE
+
+This module is free software and is published under the same
+terms as Perl itself.
@@ -0,0 +1,135 @@
+package Dancer::Hook;
+
+use strict;
+use warnings;
+use Carp;
+
+use base 'Dancer::Object';
+
+__PACKAGE__->attributes(qw/name code properties/);
+
+use Dancer::Factory::Hook;
+use Dancer::Hook::Properties;
+
+sub new {
+    my ($class, @args) = @_;
+
+    my $self = bless {}, $class;
+
+    if (!scalar @args) {
+        croak "one name and a coderef are required";
+    }
+
+    my $hook_name = shift @args;
+
+    # XXX at the moment, we have a filer position named "before_template".
+    # this one is renamed "before_template_render", so we need to alias it.
+    # maybe we need to deprecate 'before_template' to enforce the use
+    # of 'hook before_template_render => sub {}' ?
+    $hook_name = 'before_template_render' if $hook_name eq 'before_template';
+
+    $self->name($hook_name);
+
+    my ( $properties, $code );
+    if ( scalar @args == 1 ) {
+        $properties = Dancer::Hook::Properties->new();
+        $code       = shift @args;
+    }
+    elsif ( scalar @args == 2 ) {
+        my $prop = shift @args;
+        $properties = Dancer::Hook::Properties->new(%$prop);
+        $code       = shift @args;
+    }
+    else {
+        croak "something's wrong";
+    }
+
+    my $compiled_filter = sub {
+        return if Dancer::SharedData->response->halted;
+
+        my $app = Dancer::App->current();
+        return unless $properties->should_run_this_app($app->name);
+
+        Dancer::Logger::core( "entering " . $hook_name . " hook" );
+        eval { $code->(@_) };
+
+        if ($@) {
+            my $err = Dancer::Error->new(
+                code    => 500,
+                title   => $hook_name . ' filter error',
+                message => "An error occured while executing the filter named $hook_name: $@"
+            );
+            return Dancer::halt( $err->render );
+        }
+    };
+
+    $self->properties($properties);
+    $self->code($compiled_filter);
+
+    Dancer::Factory::Hook->instance->register_hook($self);
+    return $self;
+}
+
+1;
+
+=head1 NAME
+
+Dancer::Hook - Class to manipulate hooks with Dancer
+
+=head1 DESCRIPTION
+
+Manipulate hooks with Dancer
+
+=head1 SYNOPSIS
+
+  # inside a plugin
+  use Dancer::Hook;
+  Dancer::Hook->register_hooks_name(qw/before_auth after_auth/);
+
+=head1 METHODS
+
+=head2 register_hook ($hook_name, [$properties], $code)
+
+    hook 'before', {apps => ['main']}, sub {...};
+
+    hook 'before' => sub {...};
+    
+Attaches a hook at some point, with a possible list of properties.
+
+Currently supported properties:
+
+=over 4
+
+=item apps
+
+    an array reference containing apps name
+
+=back
+
+=head2 register_hooks_name
+
+Add a new hook name, so developpers of application can insert some code at this point.
+
+    package My::Dancer::Plugin;
+    Dancer::Hook->instance->register_hooks_name(qw/before_auth after_auth/);
+
+=head2 hook_is_registered
+
+Test if a hook with this name has already been registered.
+
+=head2 execute_hooks
+
+Execute a list of hooks for some position    
+
+=head2 get_hooks_for
+
+Returns the list of coderef registered for a given position
+
+=head1 AUTHORS
+
+This module has been written by Alexis Sukrieh and others.
+
+=head1 LICENSE
+
+This module is free software and is published under the same
+terms as Perl itself.
@@ -10,7 +10,7 @@ use Dancer::Timer;
 use Dancer::Config 'setting';
 use POSIX qw/strftime/;
 
-# This is the only method to implement if logger engines.
+# This is the only method to implement by logger engines.
 # It receives the following arguments:
 # $msg_level, $msg_content, it gets called only if the configuration allows
 # a message of the given level to be logged.
@@ -5,7 +5,7 @@ use Carp;
 
 use base 'Exporter';
 use Dancer::Config 'setting';
-
+use Dancer::Hook;
 use base 'Exporter';
 use vars qw(@EXPORT);
 
@@ -20,7 +20,7 @@ sub register($&);
 
 my $_keywords = {};
 
-sub add_hook { Dancer::Route::Registry->hook(@_) }
+sub add_hook { Dancer::Hook->new(@_) }
 
 sub plugin_setting {
     my $plugin_orig_name = caller();
@@ -7,6 +7,7 @@ use HTTP::Headers;
 use Dancer::Route;
 use Dancer::HTTP;
 use Dancer::Cookie;
+use Dancer::Factory::Hook;
 use Dancer::Cookies;
 use Dancer::Request;
 use Dancer::Response;
@@ -17,6 +18,10 @@ use Dancer::SharedData;
 use Dancer::Logger;
 use Dancer::MIME;
 
+Dancer::Factory::Hook->instance->install_hooks(
+    qw/before after before_serializer after_serializer before_file_render after_file_render/
+);
+
 sub render_file { get_file_response() }
 
 sub render_action {
@@ -83,10 +88,10 @@ sub get_action_response {
     my $handler =
       Dancer::App->find_route_through_apps(Dancer::SharedData->request);
 
+    my $app = ($handler && $handler->app) ? $handler->app : Dancer::App->current();
+
     # run the before filters, before "running" the route handler
-    my $app = Dancer::App->current;
-    $app = $handler->{app} if ($handler);
-    $_->() for @{$app->registry->hooks->{before}};
+    Dancer::Factory::Hook->instance->execute_hooks('before');
 
     # recurse if something has changed
     my $MAX_RECURSIVE_LOOP = 10;
@@ -116,13 +121,11 @@ sub get_action_response {
         # a response may exist, produced by a before filter
         return serialize_response_if_needed() if defined $response && $response->exists;
         # else, get the route handler's response
-        Dancer::App->current($handler->app);
-        my $response = $handler->run($request);
-        return undef unless $response; # 404
-
+        Dancer::App->current($handler->{app});
+        $handler->run($request);
         serialize_response_if_needed();
         my $resp = Dancer::SharedData->response();
-        $_->($resp) for (@{$app->registry->hooks->{after}});
+        Dancer::Factory::Hook->instance->execute_hooks('after', $resp);
         return $resp;
     }
     else {
@@ -132,8 +135,12 @@ sub get_action_response {
 
 sub serialize_response_if_needed {
     my $response = Dancer::SharedData->response();
-    $response = Dancer::Serializer->process_response($response)
-      if Dancer::App->current->setting('serializer') && $response->content();
+
+    if (Dancer::App->current->setting('serializer') && $response->content()){
+        Dancer::Factory::Hook->execute_hooks('before_serializer', $response);
+        Dancer::Serializer->process_response($response);
+        Dancer::Factory::Hook->execute_hooks('after_serializer', $response);
+    }
     return $response;
 }
 
@@ -142,8 +149,9 @@ sub get_file_response {
     my $path_info   = $request->path_info;
     my $app         = Dancer::App->current;
     my $static_file = path($app->setting('public'), $path_info);
-    return Dancer::Renderer->get_file_response_for_path($static_file, undef,
-                                                        $request->content_type);
+
+    return Dancer::Renderer->get_file_response_for_path( $static_file, undef,
+        $request->content_type );
 }
 
 sub get_file_response_for_path {
@@ -151,6 +159,9 @@ sub get_file_response_for_path {
     $status ||= 200;
 
     if ( -f $static_file ) {
+        Dancer::Factory::Hook->execute_hooks( 'before_file_render',
+            $static_file );
+
         my $fh = open_file( '<', $static_file );
         binmode $fh;
         my $response = Dancer::SharedData->response() || Dancer::Response->new();
@@ -158,6 +169,9 @@ sub get_file_response_for_path {
         $response->header('Content-Type' => (($mime && _get_full_mime_type($mime)) ||
                                              _get_mime_type($static_file)));
         $response->content($fh);
+
+        Dancer::Factory::Hook->execute_hooks( 'after_file_render', $response );
+
         return $response;
     }
     return;
@@ -6,6 +6,7 @@ use Carp;
 
 use base 'Dancer::Object';
 
+use Dancer::Config 'setting';
 use Dancer::Request::Upload;
 use Dancer::SharedData;
 use Encode;
@@ -14,9 +15,9 @@ use URI;
 use URI::Escape;
 
 my @http_env_keys = (
-    'user_agent',      'host',       'accept_language', 'accept_charset',
+    'user_agent',      'accept_language', 'accept_charset',
     'accept_encoding', 'keep_alive', 'connection',      'accept',
-    'accept_type',     'referer',
+    'accept_type',     'referer',  #'host', managed manually
 );
 my $count = 0;
 
@@ -36,13 +37,28 @@ sub agent                 { $_[0]->user_agent }
 sub remote_address        { $_[0]->address }
 sub forwarded_for_address { $_[0]->env->{'X_FORWARDED_FOR'} }
 sub address               { $_[0]->env->{REMOTE_ADDR} }
+sub host {
+    if (@_==2) {
+        $_[0]->{host} = $_[1];
+    } else {
+        my $host;
+        $host = $_[0]->env->{X_FORWARDED_HOST} if setting('behind_proxy');
+        $host || $_[0]->{host} || $_[0]->env->{HTTP_HOST};
+    }
+}
 sub remote_host           { $_[0]->env->{REMOTE_HOST} }
 sub protocol              { $_[0]->env->{SERVER_PROTOCOL} }
 sub port                  { $_[0]->env->{SERVER_PORT} }
 sub request_uri           { $_[0]->env->{REQUEST_URI} }
 sub user                  { $_[0]->env->{REMOTE_USER} }
 sub script_name           { $_[0]->env->{SCRIPT_NAME} }
-sub scheme                { $_[0]->env->{'psgi.url_scheme'} }
+sub scheme                {
+    my $scheme;
+    if (setting('behind_proxy')) {
+        $scheme = $_[0]->env->{'X_FORWARDED_PROTOCOL'} || $_[0]->env->{'HTTP_FORWARDED_PROTO'}
+    }
+    return $scheme || $_[0]->env->{'psgi.url_scheme'} || $_[0]->env->{'PSGI.URL_SCHEME'};
+}
 sub secure                { $_[0]->scheme eq 'https' }
 sub uri                   { $_[0]->request_uri }
 
@@ -97,10 +113,11 @@ sub new_for_request {
     $params ||= {};
     $method = uc($method);
 
-    my $req =
-      $class->new({%ENV, PATH_INFO => $path, REQUEST_METHOD => $method});
-    $req->{params} = {%{$req->{params}}, %{$params}};
-    $req->{body} = $body if defined $body;
+    my $req = $class->new( { %ENV,
+                             PATH_INFO      => $path,
+                             REQUEST_METHOD => $method});
+    $req->{params}  = {%{$req->{params}}, %{$params}};
+    $req->{body}    = $body    if defined $body;
     $req->{headers} = $headers if $headers;
 
     return $req;
@@ -118,6 +135,11 @@ sub forward {
     my $new_params  = _merge_params(scalar($request->params),
                                     $to_data->{params} || {});
 
+    if (exists($to_data->{options}{method})) {
+        die unless _valid_method($to_data->{options}{method});
+        $new_request->{method} = uc $to_data->{options}{method};
+    }
+
     $new_request->{params}  = $new_params;
     $new_request->{body}    = $request->body;
     $new_request->{headers} = $request->headers;
@@ -125,6 +147,11 @@ sub forward {
     return $new_request;
 }
 
+sub _valid_method {
+    my $method = shift;
+    return $method =~ /^(?:head|post|get|put|delete)$/i;
+}
+
 sub _merge_params {
     my ($params, $to_add) = @_;
 
@@ -145,13 +172,11 @@ sub base {
 sub _common_uri {
     my $self = shift;
 
-    my @env_names = qw(
-      SERVER_NAME HTTP_HOST SERVER_PORT SCRIPT_NAME psgi.url_scheme
-    );
-
-    my ($server, $host, $port, $path, $scheme) = @{$self->env}{@env_names};
-
-    $scheme ||= $self->{'env'}{'PSGI.URL_SCHEME'};    # Windows
+    my $path   = $self->env->{SCRIPT_NAME};
+    my $port   = $self->env->{SERVER_PORT};
+    my $server = $self->env->{SERVER_NAME};
+    my $host   = $self->host;
+    my $scheme = $self->scheme;
 
     my $uri = URI->new;
     $uri->scheme($scheme);
@@ -549,7 +574,7 @@ objects. It uses the environment hash table given to build the request object.
 
 =head2 new_for_request($method, $path, $params, $body, $headers)
 
-An alternate constructor convinient for test scripts which creates a request
+An alternate constructor convienient for test scripts which creates a request
 object with the arguments given.
 
 =head2 forward($request, $new_location)
@@ -558,9 +583,16 @@ Create a new request which is a clone of the current one, apart
 from the path location, which points instead to the new location.
 This is used internally to chain requests using the forward keyword.
 
+Note that the new location should be a hash reference. Only one key is
+required, the C<to_url>, that should point to the URL that forward
+will use. Optional values are the key C<params> to a hash of
+parameters to be added to the current request parameters, and the key
+C<options> that points to a hash of options about the redirect (for
+instance, C<method> pointing to a new request method).
+
 =head2 to_string()
 
-Return a string represeting the request object (eg: C<"GET /some/path">)
+Return a string representing the request object (eg: C<"GET /some/path">)
 
 =head2 method()
 
@@ -609,7 +641,7 @@ Return the scheme of the request
 
 =head2 secure()
 
-Return true of false, indicating wether the connection is secure
+Return true of false, indicating whether the connection is secure
 
 =head2 is_get()
 
@@ -758,7 +790,7 @@ table provided by C<uploads()>. It looks at the calling context and returns a
 corresponding value.
 
 If you have many file uploads under the same name, and call C<upload('name')> in
-an array context, the accesor will unroll the ARRA ref for you:
+an array context, the accesor will unroll the ARRAY ref for you:
 
     my @uploads = request->upload('many_uploads'); # OK
 
@@ -793,6 +825,10 @@ Dancer::Request object through specific accessors, here are those supported:
 
 =item C<forwarded_for_address>
 
+=item C<forwarded_protocol>
+
+=item C<forwarded_host>
+
 =item C<host>
 
 =item C<keep_alive>
@@ -39,9 +39,15 @@ sub status {
     my $self = shift;
 
     if (scalar @_ > 0) {
-        return $self->{status} = Dancer::HTTP->status(shift);
-    }
-    else {
+        my $status = shift;
+        my $numeric_status = Dancer::HTTP->status($status);
+        if ($numeric_status) {
+            return $self->{status} = $numeric_status;
+        } else {
+            carp "Unrecognised HTTP status $status";
+            return;
+        }
+    } else {
         return $self->{status};
     }
 }
@@ -63,9 +69,10 @@ sub has_passed {
 }
 
 sub forward {
-    my ($self, $uri, $params) = @_;
-    $self->{forward} = { to_url => $uri,
-                         params => $params };
+    my ($self, $uri, $params, $opts) = @_;
+    $self->{forward} = { to_url  => $uri,
+                         params  => $params,
+                         options => $opts };
 }
 
 sub is_forwarded {
@@ -6,12 +6,7 @@ use Dancer::Route;
 use base 'Dancer::Object';
 use Dancer::Logger;
 
-Dancer::Route::Registry->attributes(
-    qw(
-      id
-      hooks
-      )
-);
+Dancer::Route::Registry->attributes(qw( id ));
 
 my $id = 1;
 
@@ -22,7 +17,6 @@ sub init {
         $self->id($id++);
     }
     $self->{routes} = {};
-    $self->{hooks}  = {};
 
     return $self;
 }
@@ -35,34 +29,8 @@ sub is_empty {
     return 1;
 }
 
-sub hook {
-    my ($class, $position, $filter) = @_;
-    return Dancer::App->current->registry->add_hook($position, $filter);
-}
-
 # replace any ':foo' by '(.+)' and stores all the named
 # matches defined in $REG->{route_params}{$route}
-sub add_hook {
-    my ($self, $position, $filter) = @_;
-
-    my $compiled_filter = sub {
-        return if Dancer::SharedData->response->halted;
-        Dancer::Logger::core("entering " . $position . " hook");
-        eval { $filter->(@_) };
-        if ($@) {
-            my $err = Dancer::Error->new(
-                code  => 500,
-                title => $position . ' filter error',
-                message =>
-                  "An error occured while executing the filter at position $position: $@"
-            );
-            return Dancer::halt($err->render);
-        }
-    };
-    push @{$self->hooks->{$position}}, $compiled_filter;
-    return $compiled_filter;
-}
-
 sub routes {
     my ($self, $method) = @_;
 
@@ -296,6 +296,7 @@ sub _build_regexp {
 
     if ($self->is_regexp) {
         $self->{_compiled_regexp} = $self->regexp || $self->pattern;
+        $self->{_compiled_regexp} = qr/^$self->{_compiled_regexp}$/;
         $self->{_should_capture} = 1;
     }
     else {
@@ -6,9 +6,12 @@ use strict;
 use warnings;
 use Dancer::ModuleLoader;
 use Dancer::Engine;
+use Dancer::Factory::Hook;
 use Dancer::Error;
 use Dancer::SharedData;
 
+Dancer::Factory::Hook->instance->install_hooks(qw/before_deserializer after_deserializer/);
+
 my $_engine;
 
 sub engine {
@@ -68,6 +71,8 @@ sub process_response {
 sub process_request {
     my ($class, $request) = @_;
 
+    Dancer::Factory::Hook->execute_hooks('before_deserializer');
+
     return $request unless engine;
     return $request
       unless engine->support_content_type($request->content_type);
@@ -90,6 +95,8 @@ sub process_request {
       ? $request->_set_body_params({%$old_params, %$new_params})
       : $request->_set_body_params($new_params);
 
+    Dancer::Factory::Hook->execute_hooks('after_deserializer');
+
     return $request;
 }
 
@@ -37,12 +37,14 @@ sub get { get_current_session() }
 
 sub read {
     my ($class, $key) = @_;
+    return unless $key;
     my $session = get_current_session();
     return $session->{$key};
 }
 
 sub write {
     my ($class, $key, $value) = @_;
+    return unless $key;
     my $session = get_current_session();
     $session->{$key} = $value;
 
@@ -4,12 +4,17 @@ use strict;
 use warnings;
 use Carp;
 
+use Dancer::Factory::Hook;
 use Dancer::Deprecation;
 use Dancer::FileUtils 'path';
 
 use base 'Dancer::Engine';
 
-# Overloads this method to implement the rendering
+Dancer::Factory::Hook->instance->install_hooks(
+    qw/before_template_render after_template_render before_layout_render after_layout_render/
+);
+
+# overloads this method to implement the rendering
 # args:   $self, $template, $tokens
 # return: a string of $template's content processed with $tokens
 sub render { confess "render not implemented" }
@@ -50,10 +55,12 @@ sub apply_renderer {
 
     $view = $self->view($view);
 
-    $_->($tokens) for (@{Dancer::App->current->registry->hooks->{before_template}});
+    Dancer::Factory::Hook->execute_hooks('before_template_render', $tokens);
 
     my $content = $self->render($view, $tokens);
 
+    Dancer::Factory::Hook->execute_hooks('after_template_render', \$content);
+    
     # make sure to avoid ( undef ) in list context return
     defined $content
       and return $content;
@@ -78,8 +85,13 @@ sub apply_layout {
 
     defined $layout or return $content;
 
+    Dancer::Factory::Hook->execute_hooks('before_layout_render', $tokens, \$content);
+
     my $full_content =
       $self->layout($layout, $tokens, $content);
+
+    Dancer::Factory::Hook->execute_hooks('after_layout_render', \$full_content);
+
     # make sure to avoid ( undef ) in list context return
     defined $full_content
       and return $full_content;
@@ -15,6 +15,7 @@ use Dancer::Deprecation;
 use Dancer::Request;
 use Dancer::SharedData;
 use Dancer::Renderer;
+use Dancer::Handler;
 use Dancer::Config;
 use Dancer::FileUtils qw(open_file);
 
@@ -235,7 +236,6 @@ sub response_headers_include {
     my $tb = Test::Builder->new;
 
     my $response = dancer_response(expand_req($req));
-
     return $tb->ok(_include_in_headers($response->headers_to_array, $expected), $test_name);
 }
 
@@ -309,7 +309,8 @@ Content-Type: text/plain
             $content =~ s/\n/\r\n/g;
         }
 
-        my $l = length $content;
+        my $l = 0;
+        $l = length $content if defined $content;
         open my $in, '<', \$content;
         $ENV{'CONTENT_LENGTH'} = $l;
         $ENV{'CONTENT_TYPE'}   = $content_type;
@@ -337,20 +338,13 @@ Content-Type: text/plain
     # then store the request
     Dancer::SharedData->request($request);
 
-    # duplicate some code from Dancer::Handler
-    my $get_action = eval {
-        Dancer::Renderer->render_file
-            || Dancer::Renderer->render_action
-              || Dancer::Renderer->render_error(404);
-    };
-    if ($@) {
-        Dancer::Error->new(
-            code    => 500,
-            title   => "Runtime Error",
-            message => $@
-        )->render();
-    }
+    # XXX this is a hack!!
+    $request = Dancer::Serializer->process_request($request)
+      if Dancer::App->current->setting('serializer');
+
+    my $get_action = Dancer::Handler::render_request($request);
     my $response = Dancer::SharedData->response();
+
     $response->content('') if $method eq 'HEAD';
     Dancer::SharedData->reset_response();
     return $response if $get_action;
@@ -28,7 +28,9 @@ That's the reason I wrote this tutorial.  While I was investigating some Python
 or L<Bottle|http://bottle.paws.de/docs/dev/index.html> I enjoyed the way they explained step by step how to build an example application
 which was a little more involved that a trivial example.
 
-Using the L<Flaskr|http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/> sample application as my inspiration (OK, shamelessly plagerized) I
+Using the
+L<Flaskr|http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/> sample
+application as my inspiration (OK, shamelessly plagiarised) I
 translated that application to the Dancer framework so I could better understand how Dancer worked. (I'm learning
 it too!)
 
@@ -366,7 +368,7 @@ I mentioned near the beginning of this tutorial that it is possible to create a
 C<layout> template. In Dancr, that layout is called C<main> and it's set up by
 putting in a directive like this:
 
-  layout 'main';
+  set layout => 'main';
 
 near the top of your web application.  What this tells Dancer's template engine
 is that it should look for a file called F<main.tt> in C<dancr/views/layouts/>
@@ -448,8 +450,7 @@ Here's the complete dancr.pl script from start to finish.
  set 'warnings'     => 1;
  set 'username'     => 'admin';
  set 'password'     => 'password';
- 
- layout 'main';
+ set 'layout'       => 'main';
  
  my $flash;
  
@@ -5,7 +5,7 @@ use warnings;
 use Carp;
 use Cwd 'realpath';
 
-our $VERSION   = '1.3040';
+our $VERSION   = '1.3050';
 our $AUTHORITY = 'SUKRIA';
 
 use Dancer::App;
@@ -14,6 +14,7 @@ use Dancer::Cookies;
 use Dancer::FileUtils;
 use Dancer::GetOpt;
 use Dancer::Error;
+use Dancer::Hook;
 use Dancer::Logger;
 use Dancer::Renderer;
 use Dancer::Route;
@@ -54,6 +55,7 @@ our @EXPORT    = qw(
   halt
   header
   headers
+  hook
   layout
   load
   load_app
@@ -96,10 +98,10 @@ our @EXPORT    = qw(
 
 # Dancer's syntax
 
-sub after           { Dancer::Route::Registry->hook('after', @_) }
+sub after           { Dancer::Hook->new('after', @_) }
 sub any             { Dancer::App->current->registry->any_add(@_) }
-sub before          { Dancer::Route::Registry->hook('before', @_) }
-sub before_template { Dancer::Route::Registry->hook('before_template', @_) }
+sub before          { Dancer::Hook->new('before', @_) }
+sub before_template { Dancer::Hook->new('before_template', @_) }
 sub captures        { Dancer::SharedData->request->params->{captures} }
 sub cookies         { Dancer::Cookies->cookies }
 sub config          { Dancer::Config::settings() }
@@ -121,10 +123,18 @@ sub halt            { Dancer::SharedData->response->halt(@_) }
 sub header          { goto &headers }
 sub push_header     { Dancer::SharedData->response->push_header(@_); }
 sub headers         { Dancer::SharedData->response->headers(@_); }
-sub layout          { set(layout => shift) }
+sub hook            { Dancer::Hook->new(@_) }
+sub layout          {
+    Dancer::Deprecation->deprecated(reason => "use 'set layout => \"value\"'",
+                                    version => '1.3050',
+                                    fatal => 0);
+    set(layout => shift) }
 sub load            { require $_ for @_ }
 sub load_app        { goto &_load_app } # goto doesn't add a call frame. So caller() will work as expected
-sub logger          { set(logger => @_) }
+sub logger          {
+    Dancer::Deprecation->deprecated(reason => "use 'set logger'",fatal => 0,version=>'1.3050');
+    set(logger => @_)
+}
 sub mime            { Dancer::MIME->instance() }
 sub mime_type       {
     Dancer::Deprecation->deprecated(reason => "use 'mime' from Dancer.pm",fatal => 1)
@@ -194,6 +204,8 @@ sub import {
     # if :syntax option exists, don't change settings
     return if $syntax_only;
 
+    $as_script = 1 if $ENV{PLACK_ENV};
+    
     Dancer::GetOpt->process_args() if !$as_script;
 
     _init_script_dir($script);
@@ -406,7 +418,7 @@ L<Dancer::Plugins>.
 
 By default, C<use Dancer> exports all the functions below plus sets up
 your app.  You can control the exporting through the normal
-L<Exporter> means.  For example:
+L<Exporter> mechanism.  For example:
 
     # Just export the route controllers
     use Dancer qw(before after get post);
@@ -415,12 +427,12 @@ L<Exporter> means.  For example:
     use Test::More;
     use Dancer qw(!pass);
 
-There are also some special tags to control exports and behavior.
+There are also some special tags to control exports and behaviour.
 
 =head2 :moose
 
-This will export everything except those functions which clash with
-Moose.  Currently that is L<after> and L<before>.
+This will export everything except functions which clash with
+Moose. Currently these are C<after> and C<before>.
 
 =head2 :syntax
 
@@ -430,10 +442,10 @@ handler.
 
 =head2 :tests
 
-This will export everything except those functions which clash with
-commonly used testing modules.  Currently that is L<pass>.
+This will export everything except functions which clash with
+commonly used testing modules. Currently these are C<pass>.
 
-These can be combined.  For example, while testing...
+It can be combined with other export pragmas. For example, while testing...
 
     use Test::More;
     use Dancer qw(:syntax :tests);
@@ -496,12 +508,12 @@ Defines a before filter:
         # do something with request, vars or params
     };
 
-The anonymous function which is given to C<before> will be executed before
-executing a route handler to handle the request.
+The anonymous function given to C<before> will be executed before executing a
+route handler to handle the request.
 
 If the function modifies the request's C<path_info> or C<method>, a new
-search for a matching route is performed and the filter is re-executed
-again. Considering that this can lead to an infinite loop, this mechanism
+search for a matching route is performed and the filter is re-executed.
+Considering that this can lead to an infinite loop, this mechanism
 is stopped after 10 times with an exception.
 
 The before filter can set a response with a redirection code (either
@@ -522,21 +534,21 @@ Defines a before_template filter:
     };
 
 The anonymous function which is given to C<before_template> will be executed
-before sending data and tokens to the template. Receives a hashref of the
+before sending data and tokens to the template. Receives a HashRef of the
 tokens that will be inserted into the template.
 
 This filter works as the C<before> and C<after> filter.
 
 =head2 cookies
 
-Accesses cookies values, which returns a hashref of L<Dancer::Cookie> objects:
+Accesses cookies values, it returns a HashRef of L<Dancer::Cookie> objects:
 
     get '/some_action' => sub {
         my $cookie = cookies->{name};
         return $cookie->value;
     };
 
-In the case you have stored something else than a scalar in your cookie:
+In the case you have stored something else than a Scalar in your cookie:
 
     get '/some_action' => sub {
         my $cookie = cookies->{oauth};
@@ -571,7 +583,7 @@ You can use abbreviations for content types. For instance:
     };
 
 Note that if you want to change the default content-type for every route, you
-have to change the setting C<content_type> instead.
+have to change the C<content_type> setting instead.
 
 =head2 dance
 
@@ -656,18 +668,26 @@ something like:
 
      return forward '/home?authorized=1';
 
-But C<forward> supports an optional hash with parameters to be added
+But C<forward> supports an optional HashRef with parameters to be added
 to the actual parameters:
 
      return forward '/home', { authorized => 1 };
 
+Finally, you can add some more options to the forward method, in a
+third argument, also as a HashRef. That option is currently
+only used to change the method of your request. Use with caution.
+
+    return forward '/home', { auth => 1 }, { method => 'POST' };
+
 =head2 from_dumper ($structure)
 
 Deserializes a Data::Dumper structure.
 
 =head2 from_json ($structure, %options)
 
-Deserializes a JSON structure. Can receive optional arguments. Thoses arguments are valid L<JSON> arguments to change the behavior of the default C<JSON::from_json> function.
+Deserializes a JSON structure. Can receive optional arguments. Those arguments 
+are valid L<JSON> arguments to change the behaviour of the default 
+C<JSON::from_json> function.
 
 =head2 from_yaml ($structure)
 
@@ -675,7 +695,9 @@ Deserializes a YAML structure.
 
 =head2 from_xml ($structure, %options)
 
-Deserializes a XML structure. Can receive optional arguments. Thoses arguments are valid L<XML::Simple> arguments to change the behavior of the default C<XML::Simple::XMLin> function.
+Deserializes a XML structure. Can receive optional arguments. These arguments 
+are valid L<XML::Simple> arguments to change the behaviour of the default 
+C<XML::Simple::XMLin> function.
 
 =head2 get
 
@@ -718,6 +740,9 @@ adds a custom header to response:
         header 'x-my-header' => 'shazam!';
     }
 
+Note that it will overwrite the old value of the header, if any. To avoid that,
+see L</push_header>.
+
 =head2 push_header
 
 Do the same as C<header>, but allow for multiple headers with the same name.
@@ -728,19 +753,172 @@ Do the same as C<header>, but allow for multiple headers with the same name.
         will result in two headers "x-my-header" in the response
     }
 
+=head2 hook
+
+Adds a hook at some position. For example :
+
+  hook before_serialization => sub {
+    my $response = shift;
+    $response->content->{generated_at} = localtime();
+  };
+
+Supported B<before> hooks (in order of execution):
+
+=over
+
+=item before_deserializer
+
+This hook receives no arguments.
+
+  hook before_deserializer {
+    ...
+  };
+
+=item before_file_render
+
+This hook receives as argument the path of the file to render.
+
+  hook before_file_render {
+    my $path = shift;
+    ...
+  };
+
+=item before_error_render
+
+This hook receives as argument a L<Dancer::Error> object.
+
+  hook before_error_render => sub {
+    my $error = shift;
+  };
+
+=item before
+
+This is an alias to C<before>.
+
+This hook receives no arguments.
+
+  before sub {
+    ...
+  };
+
+is equivalent to
+
+  hook before sub {
+    ...
+  };
+
+=item before_template_render
+
+This is an alias to 'before_template'.
+
+This hook receives as argument a HashRef, containing the tokens.
+
+  hook before_template_render sub {
+    my $tokens = shift;
+    delete $tokens->{user};
+  };
+
+is equivalent to 
+
+  hook before_template sub {
+    my $tokens = shift;
+    delete $tokens->{user};
+  };
+
+=item before_layout_render
+
+This hook receives two arguments. The first one is a HashRef containing the
+tokens. The second is a ScalarRef representing the content of the template.
+
+  hook before_layout_render sub {
+    my ($tokens, $html_ref) = @_;
+    ...
+  };
+
+=item before_serialization
+
+This hook receives as argument a L<Dancer::Response> object.
+
+  hook before_serializer sub {
+    my $response = shift;
+    $response->content->{start_time} = time();
+  };
+
+=back
+
+Supported B<after> hooks (in order of execution):
+
+=over
+
+=item after_deserializer
+
+This hook receives no arguments.
+
+  hook after_deserializer sub {
+    ...
+  };
+
+=item after_file_render
+
+This hook receives as argument a L<Dancer::Response> object.
+
+  hook after_file_render sub {
+    my $response = shift;
+  };
+
+=item after_template_render
+
+This hook receives as argument a ScalarRef representing the content generated
+by the template.
+
+  hook after_template_render sub {
+    my $html_ref = shift;
+  };
+
+=item after_layout_render
+
+This hook receives as argument a ScalarRef representing the content generated
+by the layout
+
+  hook after_layout_render sub {
+    my $html_ref = shift;
+  };
+
+=item after
+
+This is an alias for 'after'.
+
+This hook receives as argument a L<Dancer::Response> object.
+
+  hook after sub {
+    my $response = shift;
+  };
+
+This is equivalent to
+
+  after sub {
+    my $response = shift;
+  };
+
+=item after_error_render
+
+This hook receives as argument a L<Dancer::Response> object.
+
+  hook after_error_render => sub {
+    my $response = shift;
+  };
+
+=back
+
 =head2 layout
 
-Allows you to set the default layout to use when rendering a view.  Syntactic
-sugar around the C<layout> setting:
+This method is deprecated. Use C<set>:
 
-    layout 'user';
+    set layout => 'user';
 
 =head2 logger
 
-Allows you to set the logger engine to use.  Syntactic sugar around the
-C<logger> setting:
-
-    logger 'console';
+Deprecated. Use C<set logger => 'console'> to change current logger engine.
 
 =head2 load
 
@@ -751,7 +929,7 @@ sugar around Perl's C<require>:
 
 =head2 load_app
 
-Loads a Dancer package. This method takes care to set the libdir to the curent
+Loads a Dancer package. This method takes care to set the libdir to the current
 C<./lib> directory:
 
     # if we have lib/Webapp.pm, we can load it like:
@@ -763,13 +941,13 @@ C<:syntax> option, in order not to change the application directory
 
 =head2 mime_type
 
-Deprecated. Check C<mime> bellow.
+Deprecated. See L</mime>.
 
 =head2 mime
 
 Shortcut to access the instance object of L<Dancer::MIME>. You should
 read the L<Dancer::MIME> documentation for full details, but the most
-commonly-used methods are summarised below:
+commonly-used methods are summarized below:
 
     # set a new mime type
     mime->add_type( foo => 'text/foo' );
@@ -793,7 +971,7 @@ commonly-used methods are summarised below:
 =head2 params
 
 I<This method should be called from a route handler>.
-Alias for the L<Dancer::Request params accessor|Dancer::Request/"params">.
+It's an alias for the L<Dancer::Request params accessor|Dancer::Request/"params">.
 
 =head2 pass
 
@@ -812,7 +990,7 @@ You should always C<return> after calling C<pass>:
 
 =head2 path
 
-Concatenates multiple path together, without worrying about the underlying
+Concatenates multiple paths together, without worrying about the underlying
 operating system:
 
     my $path = path(dirname($0), 'lib', 'File.pm');
@@ -915,7 +1093,6 @@ instance, to use a custom layout:
 
     render_with_layout $content, {}, { layout => 'layoutname' };
 
-
 =head2 request
 
 Returns a L<Dancer::Request> object representing the current request.
@@ -988,8 +1165,8 @@ Creates or updates cookie values:
 
     get '/some_action' => sub {
         set_cookie name => 'value',
-            expires => (time + 3600),
-            domain  => '.foo.com';
+                   expires => (time + 3600),
+                   domain  => '.foo.com';
     };
 
 In the example above, only 'name' and 'value' are mandatory.
@@ -1004,8 +1181,8 @@ You can also store more complex structure in your cookies:
         };
     };
 
-You can't store more complex structure than this. All your keys in your hash
-should be scalars; storing references will not work.
+You can't store more complex structure than this. All keys in the HashRef
+should be Scalars; storing references will not work.
 
 See L<Dancer::Cookie> for further options when creating your cookie.
 
@@ -1052,7 +1229,7 @@ which includes wildcards:
 
 There is also the extensive splat (A.K.A. "megasplat"), which allows extensive
 greedier matching, available using two asterisks. The additional path is broken
-down and returned as an arrayref:
+down and returned as an ArrayRef:
 
     get '/entry/*/tags/**' => sub {
         my ( $entry_id, $tags ) = splat;
@@ -1117,8 +1294,8 @@ Tells the route handler to build a response with the current template engine:
     };
 
 The first parameter should be a template available in the views directory, the
-second one (optional) is a hashref of tokens to interpolate, and the third
-(again optional) is a hashref of options.
+second one (optional) is a HashRef of tokens to interpolate, and the third
+(again optional) is a HashRef of options.
 
 For example, to disable the layout for a specific request:
 
@@ -1134,7 +1311,7 @@ Serializes a structure with Data::Dumper.
 =head2 to_json ($structure, %options)
 
 Serializes a structure to JSON. Can receive optional arguments. Thoses arguments
-are valid L<JSON> arguments to change the behavior of the default
+are valid L<JSON> arguments to change the behaviour of the default
 C<JSON::to_json> function.
 
 =head2 to_yaml ($structure)
@@ -1144,7 +1321,7 @@ Serializes a structure to YAML.
 =head2 to_xml ($structure, %options)
 
 Serializes a structure to XML. Can receive optional arguments. Thoses arguments
-are valid L<XML::Simple> arguments to change the behavior of the default
+are valid L<XML::Simple> arguments to change the behaviour of the default
 C<XML::Simple::XMLout> function.
 
 =head2 true
@@ -1162,20 +1339,20 @@ L<Dancer::Request::Upload> object. You can access all parsed uploads via:
     };
 
 If you named multiple input of type "file" with the same name, the upload
-keyword will return an array of Dancer::Request::Upload objects:
+keyword will return an Array of Dancer::Request::Upload objects:
 
     post '/some/route' => sub {
         my ($file1, $file2) = upload('files_input');
         # $file1 and $file2 are Dancer::Request::Upload objects
     };
 
-You can also access the raw hashref of parsed uploads via the current request
+You can also access the raw HashRef of parsed uploads via the current request
 object:
 
     post '/some/route' => sub {
         my $all_uploads = request->uploads;
         # $all_uploads->{'file_input_foo'} is a Dancer::Request::Upload object
-        # $all_uploads->{'files_input'} is an array ref of Dancer::Request::Upload objects
+        # $all_uploads->{'files_input'} is an ArrayRef of Dancer::Request::Upload objects
     };
 
 Note that you can also access the filename of the upload received via the params
@@ -1229,7 +1406,7 @@ C<vars> keyword.
 
 =head2 vars
 
-Returns the hashref of all shared variables set during the filter/route
+Returns the HashRef of all shared variables set during the filter/route
 chain:
 
     get '/path' => sub {
@@ -1279,6 +1456,8 @@ The following modules are mandatory (Dancer cannot run without them):
 
 =item L<HTTP::Body>
 
+=item L<LWP>
+
 =item L<MIME::Types>
 
 =item L<URI>
@@ -1289,11 +1468,15 @@ The following modules are optional:
 
 =over 8
 
-=item L<Template> : In order to use TT for rendering views
+=item L<JSON> : needed to use JSON serializer
 
-=item L<YAML> : needed for configuration file support
+=item L<Plack> : in order to use PSGI
+
+=item L<Template> : in order to use TT for rendering views
 
-=item L<File::MimeInfo::Simple>
+=item L<XML::Simple> and L<XML:SAX> or L<XML:Parser> for XML serialization
+
+=item L<YAML> : needed for configuration file support
 
 =back
 
@@ -53,14 +53,31 @@ version_check() if $do_check_dancer_version;
 safe_mkdir($DANCER_APP_DIR);
 create_node( app_tree($name), $DANCER_APP_DIR );
 
+unless (eval "require YAML") {
+    print <<NOYAML;
+*****
+WARNING: YAML.pm is not installed.  This is not a full dependency, but is highly
+recommended; in particular, the scaffolded Dancer app being created will not be
+able to read settings from the config file without YAML.pm being installed.
+
+To resolve this, simply install YAML from CPAN, for instance using one of the
+following commands:
+
+  cpan YAML
+  perl -MCPAN -e 'install YAML'
+  curl -L http://cpanmin.us | perl - --sudo YAML
+*****
+NOYAML
+}
+
 # subs
 
 sub validate_app_name {
     my $name = shift;
     if ($name =~ /[^\w:]/ || $name =~ /^\d/ || $name =~ /\b:\b|:{3,}/) {
         print STDERR "Error: Invalid application name.\n";
-        print STDERR "Application names must not contain colons,"
-            ." dots or start with a number.\n";
+        print STDERR "Application names must not contain single colons,"
+            ." dots, hyphens or start with a number.\n";
         exit;
     }
 }
@@ -561,12 +578,11 @@ true;
 'style.css' =>
 '
 body {
-font-family: \'Linux Libertine\', Palatino, \'Palatino Linotype\', \'Book Antiqua\', Georgia, \'Times New Roman\', serif;
 margin: 0;
 margin-bottom: 25px;
 padding: 0;
 background-color: #ddd;
-background-image: url("/images/perldancer-bg.jpg"); 
+background-image: url("/images/perldancer-bg.jpg");
 background-repeat: no-repeat;
 background-position: top left;
 
@@ -587,7 +603,6 @@ color: white;
 text-decoration: none;
 }
 
-
 #page {
 background-color: #ddd;
 width: 750px;
@@ -617,7 +632,7 @@ padding-right: 30px;
 
 
 #header {
-background-image: url("/images/perldancer.jpg"); 
+background-image: url("/images/perldancer.jpg");
 background-repeat: no-repeat;
 background-position: top left;
 height: 64px;
@@ -629,7 +644,6 @@ font-weight: normal;
 font-size: 16px;
 }
 
-
 #about h3 {
 margin: 0;
 margin-bottom: 10px;
@@ -664,7 +678,6 @@ margin: 0;
 padding: 10px;
 }
 
-
 #getting-started {
 border-top: 1px solid #ccc;
 margin-top: 25px;
@@ -701,7 +714,6 @@ color: #555;
 font-size: 13px;
 }
 
-
 #search {
 margin: 0;
 padding-top: 10px;
@@ -714,7 +726,6 @@ margin: 2px;
 }
 #search-text {width: 170px}
 
-
 #sidebar ul {
 margin-left: 0;
 padding-left: 0;
@@ -732,7 +743,6 @@ list-style-type: none;
 margin-bottom: 5px;
 }
 
-
 h1, h2, h3, h4, h5 {
 font-family: sans-serif;
 margin: 1.2em 0 0.6em 0;
@@ -23,6 +23,7 @@ my @keywords = qw(
     halt
     header
     headers
+    hook
     layout
     load
     load_app
@@ -27,7 +27,7 @@ my @tests = (
       expected => sub { setting('auto_reload') == 0}},
 );
 
-plan tests => scalar(@tests) + 1;
+plan tests => scalar(@tests) + 3;
 
 foreach my $test (@tests) {
     @ARGV = @{ $test->{args}};
@@ -37,3 +37,14 @@ foreach my $test (@tests) {
 }
 
 ok(Dancer::GetOpt->print_usage());
+
+# Dancer->import process ARGV
+@ARGV = ('--port=1234');
+Dancer->import();
+is setting('port'), 1234, "->import process ARGV";
+
+# Dancer->import doesn't process ARGV when PLACK_ENV is set (GH#473)
+@ARGV = ('--port=4321');
+$ENV{PLACK_ENV} = 'development';
+Dancer->import();
+is setting('port'), 1234, "->import doesn't process ARGV";
@@ -9,10 +9,10 @@ use File::Spec qw/catfile/;
 my $dir = tempdir(CLEANUP => 1, TMPDIR => 1);
 set appdir => $dir;
 
-eval { logger 'foobar' };
+eval { set logger => 'foobar' };
 like($@, qr/unknown logger/, 'invalid logger detected');
 
-ok(logger('file'), 'file-based logger correctly set');
+ok(set(logger => 'file'), 'file-based logger correctly set');
 
 my $message = 'this is a test log message';
 
@@ -35,7 +35,7 @@ ok(grep(/warn \@.*$message/, @content), 'warning message found');
 ok(grep(/error \@.*$message/, @content), 'error message found');
 
 set environment => 'test';
-logger 'file';
+set logger => 'file';
 
 my $test_logfile = Dancer::FileUtils::d_catfile($logdir, "test.log");
 ok((-r $test_logfile), "environment logfile exists");
@@ -1,73 +0,0 @@
-use strict;
-use warnings;
-
-use Test::More tests => 17, import => ['!pass'];
-use Dancer ':syntax';
-use Dancer::Test;
-
-my $i = 0;
-
-ok(
-    before(
-        sub {
-            content_type('text/xhtml');
-        }
-    )
-);
-
-ok(
-    before(
-        sub {
-            if ( request->path_info eq '/redirect_from' ) {
-                redirect('/redirect_to');
-            }
-            else {
-                params->{number} = 42;
-                var notice => "I am here";
-                request->path_info('/');
-            }
-        }
-    ),
-    'before filter is defined'
-);
-
-ok(
-    get(
-        '/' => sub {
-            is( params->{number}, 42,             "params->{number} is set" );
-            is( "I am here",      vars->{notice}, "vars->{notice} is set" );
-            return 'index';
-        }
-    ),
-    'index route is defined'
-);
-
-ok(
-    get(
-        '/redirect_from' => sub {
-            $i++;
-        }
-    )
-);
-
-route_exists [GET => '/'];
-response_exists [GET => '/'];
-
-my $path = '/somewhere';
-my $request = [ GET => $path ];
-
-route_doesnt_exist $request, 
-    "there is no route handler for $path...";
-
-response_exists $request,
-    "...but a response is returned though";
-
-response_content_is $request, 'index', 
-    "which is the result of a redirection to /";
-
-response_headers_include [GET => '/redirect_from'] => [
-    'Location' => 'http://localhost/redirect_to',
-    'Content-Type' => 'text/xhtml',
-];
-
-is $i, 0, 'never gone to redirect_from';
@@ -1,6 +1,6 @@
 use strict;
 use warnings;
-use Test::More tests => 9, import => ['!pass'];
+use Test::More tests => 10, import => ['!pass'];
 
 use Dancer ':syntax';
 use Dancer::Test;
@@ -24,10 +24,12 @@ foreach my $test (@tests) {
     my $handle;
     my $path = $test->{path};
     my $expected = $test->{expected};
- 
+
     my $request = [GET => $path];
-       
+
     response_exists($request, "route handler found for path `$path'");
-    response_content_is_deeply($request, $expected, 
+    response_content_is_deeply($request, $expected,
         "match data for path `$path' looks good");
 }
+
+response_status_is [GET => '/no/hello/bar'] => 404;
@@ -39,5 +39,16 @@ $expected_headers = [
 ];
 response_headers_include [GET => '/redirect_querystring'] => $expected_headers;
 
+set behind_proxy => 1;
+$ENV{X_FORWARDED_HOST} = "nice.host.name";
+response_headers_include [GET => '/bounce'] => [Location => 'http://nice.host.name/'];
+
+$ENV{HTTP_FORWARDED_PROTO} = "https";
+response_headers_include [GET => '/bounce'] => [Location => 'https://nice.host.name/'];
+
+$ENV{X_FORWARDED_PROTOCOL} = "ftp";  # stupid, but why not?
+response_headers_include [GET => '/bounce'] => [Location => 'ftp://nice.host.name/'];
+
+
 Dancer::Logger::logger->{fh}->close;
 File::Temp::cleanup();
@@ -1,28 +0,0 @@
-use strict;
-use warnings;
-
-use Test::More import => ['!pass'];
-use Dancer ':syntax';
-use Dancer::Test;
-
-plan tests => 4;
-
-ok(
-    after sub {
-        my $response = shift;
-        $response->{content} = 'not index!';
-    },
-    'after hook is defined'
-);
-
-ok(
-    get(
-        '/' => sub {
-            return 'index';
-        }
-    ),
-    'index route is defined'
-);
-
-route_exists [ GET => '/' ];
-response_content_is( [ GET => '/' ], 'not index!' );
@@ -1,6 +1,6 @@
 use strict;
 use warnings;
-use Test::More tests => 14, import => ['!pass'];
+use Test::More tests => 16, import => ['!pass'];
 
 use Dancer ':syntax';
 use Dancer::Logger;
@@ -14,7 +14,7 @@ Dancer::Logger->init('File');
 # checking get
 
 get '/'        => sub { 
-    'home' . join(',', params);
+    'home:' . join(',', params);
 };
 get '/bounce/' => sub {
     return forward '/';
@@ -25,21 +25,30 @@ get '/bounce/:withparams/' => sub {
 get '/bounce2/adding_params/' => sub {
     return forward '/', { withparams => 'foo' };
 };
+post '/simple_post_route/' => sub {
+    'post:' . join(',', params);
+};
+get '/go_to_post/' => sub {
+    return forward '/simple_post_route/', { foo => 'bar' }, { method => 'post' };
+};
 
 response_exists     [ GET => '/' ];
-response_content_is [ GET => '/' ], 'home';
+response_content_is [ GET => '/' ], 'home:';
 
 response_exists     [ GET => '/bounce/' ];
-response_content_is [ GET => '/bounce/' ], 'home';
+response_content_is [ GET => '/bounce/' ], 'home:';
 
 response_exists     [ GET => '/bounce/thesethings/' ];
-response_content_is [ GET => '/bounce/thesethings/' ], 'homewithparams,thesethings';
+response_content_is [ GET => '/bounce/thesethings/' ], 'home:withparams,thesethings';
 
 response_exists     [ GET => '/bounce2/adding_params/' ];
-response_content_is [ GET => '/bounce2/adding_params/' ], 'homewithparams,foo';
+response_content_is [ GET => '/bounce2/adding_params/' ], 'home:withparams,foo';
+
+response_exists     [ GET => '/go_to_post/' ];
+response_content_is [ GET => '/go_to_post/' ], 'post:foo,bar';
 
 my $expected_headers = [
-    'Content-Length' => 4,
+    'Content-Length' => 5,
     'Content-Type' => 'text/html',
     'X-Powered-By' => "Perl Dancer ${Dancer::VERSION}",
 ];
@@ -63,12 +63,12 @@ get '/solo' => sub {
 };
 
 get '/full' => sub {
-    layout 'main';
+    set layout => 'main';
     template 't03';
 };
 
 get '/layoutdisabled' => sub {
-    layout 'main';
+    set layout => 'main';
     template 't03', {}, { layout => undef };
 };
 
@@ -1,44 +0,0 @@
-use strict;
-use warnings;
-
-use Test::More tests => 10, import => ['!pass'];
-use Dancer ':syntax';
-use Dancer::Test;
-
-ok(
-    before_template sub {
-        my $tokens = shift;
-        $tokens->{foo} = 'bar';
-    }
-);
-
-setting views => path('t', '10_template', 'views');
-
-ok(
-    get '/' => sub {
-        template 'index', {foo => 'baz'};
-    }
-);
-
-route_exists [ GET => '/' ];
-response_content_like( [ GET => '/' ], qr/foo => bar/ );
-
-ok(
-    get '/layout_empty_params_passed' => sub {
-        layout 'main';
-        template 'index', {};
-    }
-);
-
-route_exists [ GET => '/layout_empty_params_passed' ];
-response_content_like( [ GET => '/layout_empty_params_passed' ], qr/layout:bar\ncontent:foo => bar/ );
-
-ok(
-    get '/layout_but_no_params_passed' => sub {
-        layout 'main';
-        template 'index';
-    }
-);
-
-route_exists [ GET => '/layout_but_no_params_passed' ];
-response_content_like( [ GET => '/layout_but_no_params_passed' ], qr/layout:bar\ncontent:foo => bar/ );
@@ -0,0 +1,30 @@
+use strict;
+use warnings;
+
+use File::Temp qw/tempdir/;
+use Test::More tests => 3, import => ['!pass'];
+
+use Dancer;
+use Dancer::FileUtils;
+use Dancer::Test;
+
+my $dir = tempdir(CLEANUP => 1, TMPDIR => 1);
+my $logfile = Dancer::FileUtils::path($dir, "logs", "development.log");
+
+set environment => 'development';
+set appdir => $dir;
+
+set log    => 'debug';
+set logger => 'file';
+
+
+get '/' => sub {
+    die "Dieing in route handler - arrggghh!";
+};
+
+response_status_is [GET => '/'], 500 => "We get a 500 answer";
+ok -f $logfile => "Log file got created";
+
+my $logcontents = Dancer::FileUtils::read_file_content($logfile);
+
+like $logcontents => qr/arrggghh!/ => "Log file includes die message";
@@ -34,11 +34,8 @@ Test::TCP::test_tcp(
         # new request, no serializer
         $res = $ua->request($request);
         ok( $res->is_success, 'Successful response from server' );
-        is_deeply(
-            $res->content,
-            "$data",
-            'Serializer undef, getting our object back',
-        );
+        like($res->content, qr/HASH\(0x.+\)/,
+            'Serializer undef, response not serialised');
     },
 
     server => sub {
@@ -23,6 +23,7 @@ set(environment => 'test' );
 
 my $conffile = Dancer::Config->conffile;
 write_file( $conffile => << 'CONF' );
+logger: Null
 plugins:
   Test:
     foo: bar
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+use Test::More import => ['!pass'];
+
+use Dancer ':syntax';
+
+plan tests => 4;
+
+ok( before( sub { 'block before' } ), 'add a before filter' );
+ok( after( sub  { 'block after' } ),  'add an after filter' );
+
+ok( before_template( sub { 'block before_template' } ),
+    'add a before_template filter' );
+
+ok(
+    hook( 'before', sub { 'block before' } ),
+    'add a before filter using the hook keyword'
+);
@@ -0,0 +1,23 @@
+use strict;
+use warnings;
+use Test::More import => ['!pass'];
+
+use Dancer ':syntax';
+
+plan tests => 5;
+
+my $cpt = 0;
+
+ok( hook( 'before' => sub { $cpt += shift || 1 }), 'add a before filter');
+
+my $app = Dancer::App->current->name;
+is scalar @{ Dancer::Factory::Hook->instance->get_hooks_for('before') }, 1, 'got one before filter';
+
+my $hooks = Dancer::Factory::Hook->instance->get_hooks_for('before');
+is scalar @$hooks, 1, 'got one before filter';
+
+Dancer::Factory::Hook->instance->execute_hooks('before');
+is $cpt, 1, 'execute hooks without args';
+
+Dancer::Factory::Hook->instance->execute_hooks( 'before', 2 );
+is $cpt, 3, 'execute hooks with one arg';
@@ -0,0 +1,71 @@
+use strict;
+use warnings;
+use Test::More import => ['!pass'];
+
+use Dancer ':syntax';
+use Dancer::Test;
+
+plan tests => 15;
+
+my $i = 0;
+
+ok(
+    before(
+        sub {
+            content_type('text/xhtml');
+        }
+    )
+);
+
+ok(
+    before(
+        sub {
+            if ( request->path_info eq '/redirect_from' ) {
+                redirect('/redirect_to');
+            }
+            else {
+                params->{number} = 42;
+                var notice => "I am here";
+                request->path_info('/');
+            }
+        }
+    ),
+    'before filter is defined'
+);
+
+get(
+    '/' => sub {
+        is( params->{number}, 42,             "params->{number} is set" );
+        is( "I am here",      vars->{notice}, "vars->{notice} is set" );
+        return 'index';
+    }
+);
+
+get(
+    '/redirect_from' => sub {
+        $i++;
+    }
+);
+
+route_exists    [ GET => '/' ];
+response_exists [ GET => '/' ];
+
+my $path = '/somewhere';
+my $request = [ GET => $path ];
+
+route_doesnt_exist $request, "there is no route handler for $path...";
+
+response_exists $request, "...but a response is returned though";
+
+response_content_is $request, 'index',
+  "which is the result of a redirection to /";
+
+response_headers_are_deeply [ GET => '/redirect_from' ],
+  [
+    'Location'     => 'http://localhost/redirect_to',
+    'Content-Type' => 'text/xhtml',
+    'X-Powered-By' => "Perl Dancer ${Dancer::VERSION}",
+  ];
+
+is $i, 0, 'never gone to redirect_from';
+
@@ -0,0 +1,27 @@
+use strict;
+use warnings;
+
+use Test::More import => ['!pass'];
+use Dancer ':syntax';
+use Dancer::Test;
+
+plan tests => 3;
+
+ok(
+    after(
+        sub {
+            my $response = shift;
+            $response->content('not index!');
+        }
+    ),
+    'after hook is defined'
+);
+
+get(
+    '/' => sub {
+        return 'index';
+    }
+);
+
+route_exists [ GET => '/' ];
+response_content_is( [ GET => '/' ], 'not index!' );
@@ -0,0 +1,38 @@
+use strict;
+use warnings;
+
+use Test::More tests => 7, import => ['!pass'];
+use Dancer ':syntax';
+use Dancer::Test;
+use Time::HiRes qw/gettimeofday/;
+
+my ( $start, $diff );
+
+ok(
+    before_template sub {
+        my $tokens = shift;
+        $tokens->{foo} = 'bar';
+        ( undef, $start ) = gettimeofday();
+    }
+);
+
+ok(
+    hook after_template_render => sub {
+        my $full_content = shift;
+        like $$full_content, qr/foo => bar/;
+        my ( undef, $end ) = gettimeofday();
+        $diff = $end - $start;
+    }
+);
+
+setting views => path( 't', '22_hooks', 'views' );
+
+get '/' => sub {
+    template 'index', { foo => 'baz' };
+};
+
+route_exists [ GET => '/' ];
+response_content_like( [ GET => '/' ], qr/foo => bar/ );
+
+ok $diff;
+cmp_ok $diff, '>', 0;
@@ -0,0 +1,35 @@
+use strict;
+use warnings;
+
+use Test::More import => ['!pass'];
+use Dancer ':syntax';
+use Dancer::Test;
+
+plan tests => 6;
+
+my $time = localtime();
+
+ok(
+    hook before_layout_render => sub {
+        my $tokens = shift;
+        $tokens->{time} = $time;
+    }
+);
+
+ok(
+    hook after_layout_render => sub {
+        my $full_content = shift;
+        like $$full_content, qr/start/;
+        like $$full_content, qr/stop/;
+    }
+);
+
+set views => path( 't', '22_hooks', 'views' );
+set layout => 'main';
+
+get '/' => sub {
+    template 'index', { foo => 'baz' };
+};
+
+route_exists [ GET => '/' ];
+response_content_like( [ GET => '/' ], qr/start $time/ );
@@ -0,0 +1,75 @@
+use strict;
+use warnings;
+
+use Test::More import => ['!pass'];
+use Dancer ':syntax';
+use Dancer::Test;
+
+use Time::HiRes qw/gettimeofday/;
+
+plan skip_all => "JSON is needed to run this tests"
+    unless Dancer::ModuleLoader->load('JSON');
+
+set serializer => 'JSON';
+
+plan tests => 11;
+
+my $body = '{"foo":"bar"}';
+
+ok(
+    hook(
+        before_deserializer => sub {
+            my $request = Dancer::SharedData->request;
+            if ($request->is_put){
+                like $request->body, qr/foo/, 'content from PUT is valid';
+            }
+        }
+    ),
+    'hook for before_serializer',
+);
+
+ok(
+    hook(
+        after_deserializer => sub {
+            my $request = Dancer::SharedData->request;
+            if ( $request->is_put ) {
+                is $request->params->{foo}, 'bar', 'content from request is ok';
+            }
+        }
+    ),
+    'hook for after_deserializer'
+);
+
+ok(
+    hook(
+        before_serializer => sub {
+            my $response = shift;
+            my ( undef, $start ) = gettimeofday;
+            $response->content->{start_time} = $start;
+        }
+    ),
+    'hook for before_serializer'
+);
+
+ok(
+    hook(
+        after_serializer => sub {
+            my $response = shift;
+            like $response->content, qr/\"start_time\" :/, 'content is ok inside hook';
+        }
+    ),
+    'hook for after_serializer'
+);
+
+get '/' => sub { { foo => 1 } };
+put '/' => sub { { foo => 1 } };
+
+route_exists [ GET => '/' ], 'route exists';
+response_content_like( [ GET => '/' ], qr/start_time/, 'content is ok' );
+
+my $response = dancer_response(
+    PUT => '/',
+    { body => $body, headers => [ 'Content-Type' => 'application/json' ] }
+);
+
+like $response->content, qr/start_time/, 'content is ok';
@@ -0,0 +1,42 @@
+use strict;
+use warnings;
+
+use Test::More import => ['!pass'];
+use Dancer ':syntax';
+use Dancer::Test;
+use Dancer::FileUtils 'read_glob_content';
+use File::Temp 'tempdir';
+
+plan tests => 5;
+
+my $dir = tempdir( CLEANUP => 1 );
+
+setting public => $dir;
+
+open my $fh, '>', File::Spec->catfile( $dir, 'test.txt' );
+print $fh "this is content";
+close $fh;
+
+ok(
+    hook 'before_file_render' => sub {
+        my $file_path = shift;
+        $file_path =~ s/foo/test.txt/;
+    }
+);
+
+ok(
+    hook 'after_file_render' => sub {
+        my $response = shift;
+        is       $response->header('Content-Type'), 'text/plain';
+       $response->header( 'Content-Type' => 'text/tests' );
+    }
+);
+
+get '/' => sub {
+     send_file('test.txt') 
+};
+
+my $response = dancer_response( GET => '/' );
+is read_glob_content( $response->content ), 'this is content';
+is $response->header('Content-Type'), 'text/tests';
+
@@ -0,0 +1,33 @@
+use strict;
+use warnings;
+
+use Test::More import => ['!pass'];
+use Dancer ':syntax';
+use Dancer::Test;
+
+plan tests => 5;
+
+my $i = 0;
+
+ok(
+    hook 'before_error_render' => sub {
+        my $error = shift;
+        is $error->code, 404;
+        $i++;
+    }
+);
+
+ok(
+    hook 'after_error_render' => sub {
+        my $response = shift;
+        is $response->status, 404;
+        $i++;
+    }
+);
+
+get '/' => sub {
+    send_error("fake error", 404);
+};
+
+my $response = dancer_response( GET => '/' );
+is $i, 2;
@@ -0,0 +1 @@
+foo => <% foo %>
@@ -0,0 +1,2 @@
+start <% time %>
+<% content %>stop
@@ -2,8 +2,8 @@ package MyAppFoo;
 
 use Dancer ':syntax';
 
-before sub {
-    halt ('before block in foo');
+before {apps => [qw/MyAppFoo/]}, sub {
+    halt('before block in foo');
 };
 
 get '/' => sub {